package Java集合;

import org.junit.Test;
import java.util.*;

/*
    Map接口概述
         Map与Collection并列存在,用于保存具有映射关系的数据:key-value
         Map中的 key和 value都可以是任何引用类型的数据
         Map中的 key用Set来存放,不允许重复,即同一个 Map对象所对应的类须重写hashCode()和equals()方法
         常用String类作为Map的"键"
         key和 value之间存在单向一对一关系,即通过指定的 key总能找到唯一的、确定的 value
         Map接口的常用实现类:
            |--- HashMap:Map接口使用频率最高的实现类,线程不安全的,效率高,key或value可以存储null值
                |---LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历(在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素),对于频繁的遍历操作,效率高于HashMap
            |--- TreeMap:保证按照添加的key-value进行排序,实现排序遍历(考虑key的自然排序或定制排序),底层使用红黑树
            |--- Hashtable:古老的实现类,线程安全的,效率低,key或value不可以存储null值
                |---Properties:常用于处理配置文件,key和value都是String类型
         HashMap底层: 数组 + 链表(jdk7及之前)  数组 + 链表 + 红黑树(jdk8)

    Map结构的理解
         Map中的key:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode()方法(针对的是HashMap)
         Map中的value:无序的、可重复的,使用Collection存储所有的value --> value所在的类要重写equals()方法
         一个键值对:key-value构成了一个Entry对象
         Map中的Entry:无序的、不可重复的,使用Set存储所有的Entry

    HashMap底层实现原理(jdb7):
         Map map = new HashMap();
         在实例化以后,底层创建了长度是16的一维数组Entry[] table
         ....
         map.put(key1,value1)
         首先,调用key1所有类的hashCode()计算key1哈希值,此哈希值经过某种算法以后,得到在Entry数组中的存放位置
         如果此位置上的数据为空,此时key1-value1添加成功  --> 情况1
         如果此位置上的数据不为空(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值
                 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功  --> 情况2
                 如果key1的哈希值与已经存在的某一个数据的哈希值相同,则调用key1所在类的equals()方法
                         如果equals()返回false:此时key1-value1添加成功  --> 情况3
                         如果equals()返回true:使用value1替换相同key的value值
         补充:
             情况2和情况3中,key1-value1和原来的数据以链表的方式存储
             在不断的添加过程中,会涉及到扩容的方式,当超出临界值,且要存放的位置非空时,就会发生扩容,默认的扩容方式是扩容为原来容量的2倍,并将原有的数据复制过来

    Map接口:常用方法
          添加、删除、修改操作
              Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中 --> 如果key相同,则会将value进行替换
              void putAll(Map m):将m中的所有key-value对存放到当前map中
              Object remove(Object key):移除指定key的key-value对,并返回value
              void clear():清空当前map中的所有数据
          元素查询的操作
             Object get(Object key):获取指定key对应的value
              boolean containsKey(Object key):是否包含指定的key
              boolean containsValue(Object value):是否包含指定的value  --> 当寻找到一个后便不会继续向下寻找
              int size():返回map中key-value对的个数
              boolean isEmpty():判断当前map是否为空
              boolean equals(Object obj):判断当前map和参数对象obj是否相等
          元视图操作的方法
             Set keySet():返回所有key构成的Set集合
             Collection values():返回所有value构成的Collection集合
             Set entrySet():返回所有key-value对构成的Set集合

         JDK8相较于JDK7在底层实现方面的不同
            HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
            当首次调用map.put()时,再创建长度为16的数组
            数组为Node类型,在jdk7中称为Entry类型
            形成链表结构时,新添加的key-value对在链表的尾部（七上八下）
            当数组指定索引位置的链表长度 >8 时,且map中的数组的长度 >64 时,此索引位置上的所有key-value对使用红黑树进行存储

         HashMap源码中的重要常量
              DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16
              MAXIMUM_CAPACITY:HashMap的最大支持容量,2^30
              DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
              TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树
              UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
              MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍)
              table:存储元素的数组,总是2的n次幂
              entrySet:存储具体元素的集
              size:HashMap中存储的键值对的数量
              modCount:HashMap扩容和结构改变的次数
              threshold:扩容的临界值=容量*填充因子
              loadFactor:填充因子

        HashMap的存储结构:JDK 1.8之前
              HashMap的内部存储结构其实是数组和链表的结合,当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之为"桶"(bucket),每个 bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素
              每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链,而且新添加的元素作为链表的head
              添加元素的过程：
                 向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据 key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i,如果位置i上没有元素,则entry1直接添加成功,如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry,如果彼此hash值不同,则直接添加成功,如果 hash值不同,继续比较二者是否equals,如果返回值为true,则使用entry1的value去替换equals为true的entry的value,如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功,entry1指向原有的entry元素
              HashMap的扩容
                 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的,所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在 HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize
              那么HashMap什么时候进行扩容呢？
                 当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)*loadFactor时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值,也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能

        HashMap的存储结构:JDK 1.8
             HashMap的内部存储结构其实是数组+链表+树的结合,当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 "桶"(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素
             每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链,也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树,而新添加的元素作为链表的last,或树的叶子结点
             那么HashMap什么时候进行扩容和树形化呢？
                  当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)*loadFactor时,就会进行数组扩容, loadFactor的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值,也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能
                  当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型,当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表

       关于映射关系的key是否可以修改? answer:不要修改
            映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上

       负载因子值的大小,对HashMap有什么影响
             负载因子的大小决定了HashMap的数据密度
             负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,,造成查询或插入时的比较次数增多,性能会下降
             负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高,但是会浪费一定的内容空间,而且经常扩容也会影响性能,建议初始化预设大一点的空间
             按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数

   LinkedHashMap底层实现原理(了解)
         LinkedHashMap是 HashMap的子类
         在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
         与LinkedHashSet类似,LinkedHashMap可以维护 Map的迭代顺序:迭代顺序与 Key-Value对的插入顺序一致


 */
public class MapTest06 {

    @Test
    public void test1(){
        Map map = new HashMap();
        map.put(null,null);
    }

    @Test
    public void test2(){
        Map map = new HashMap();
        map.put(1,"AA");
        map.put(3,"BB");
        map.put(2,"CC");
        System.out.println(map);

        //返回所有key构成的Set集合
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(map.get(iterator.next()));
        }

        //返回所有value构成的Collection集合
        Collection coll = map.values();
        Iterator ite1 = coll.iterator();
        while(ite1.hasNext()){
            System.out.println(ite1.next());
        }

        //返回所有key-value对构成的Set集合
        Set entrySet = map.entrySet();
        Iterator entryIterator = entrySet.iterator();
        while (entryIterator.hasNext()){
            //entrySet集合中的元素都是entry
            Object obj = entryIterator.next();
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry.getKey() + "--->" + entry.getValue());
        }
    }

    /*
        向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序
     */
    //自然排序
    @Test
    public void test3(){
        TreeMap map = new TreeMap();
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);

        map.put(u1,97);
        map.put(u2,98);
        map.put(u3,76);
        map.put(u4,100);

        Set set = map.entrySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            Map.Entry entry = (Map.Entry)next;
            System.out.println(entry.getKey() + "-->" + entry.getValue());
        }
    }

    //定制排序
    @Test
    public void  test4(){
        //
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }
                throw new RuntimeException("类型不匹配!");
            }
        });
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);

        map.put(u1,97);
        map.put(u2,98);
        map.put(u3,76);
        map.put(u4,100);

        Set set = map.entrySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            Map.Entry entry = (Map.Entry)next;
            System.out.println(entry.getKey() + "-->" + entry.getValue());
        }
    }

}

class User implements Comparable{

    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
            if(user.getName().equals(this.getName())){
                return Integer.compare(this.age,user.getAge());
            }
            return -this.getName().compareTo(user.getName());
        }
        throw new RuntimeException("类型不匹配!");
    }
}


